﻿/**
*    Copyright (C) 2008  Chase Kernan 
*    chase.kernan@gmail.com
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
* 
**/

package com.chasekernan.hxnova.client.mapviewer;

import com.chasekernan.hxnova.core.dataholders.FleetHolder;
import com.chasekernan.hxnova.core.dataholders.StarsHolder;
import com.chasekernan.hxnova.core.dataholders.Global;
import com.chasekernan.hxnova.core.players.Player;
import com.chasekernan.hxnova.core.players.PlayerRelations;
import com.chasekernan.hxnova.utils.Utils;
import com.chasekernan.hxnova.core.races.Race;
import com.chasekernan.hxnova.core.stars.Star;
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.Lib;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.geom.Rectangle;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
import com.chasekernan.hxnova.utils.Vector;

typedef RGB = {
    var rb : Int;
    var gb : Int;
    var bb : Int;
}

typedef ScanPoint = {
    var location : Vector;
    var radius : Int;
}

class MapImage extends Sprite {
    
    public static var DEFAULT_WIDTH = 700;
    public static var DEFAULT_HIEGHT = 600;
    public static var DEFAULT_BACKGROUND_COLOR = 0x111111;
    
    public static var INHERENT_ZOOM = 1.5;
    
    public static var GRID_COLOR = 0xFFFFFF;
    public static var GRID_SPACING = 100;
    
    public static var MAX_GREEN_STAR = 0x00FF00;
    public static var MIN_GREEN_STAR = 0x787a50;
    public static var MAX_RED_STAR = 0xff0000;
    public static var MIN_RED_STAR = 0xff8000;
    
    public static var NORMAL_SCAN_COLOR = 0x661c1c;
    public static var PENETRATING_SCAN_COLOR = 0x656300;
    
    public static var UNKNOWN_STAR = 0x999999;
    
    public static var MAX_RADIUS = 5;
    public static var MIN_POP_RADIUS = 2;
    public static var HW_RADIUS = 6;
    public static var MIN_RADIUS = 1;
    public static var LINE_THICKNESS = 1.25;
    public static var GREEN_ADDED_THICKNESS = 0.25;
    
    public static var FLAG_LINE = 0.25;
    
    public static var NORMAL_ZOOM_FACTOR = 10;
    public static var NORMAL_CAMERA_ZOOM = 3.0;
    
    public static var FRIENDLY_COLOR = 0xcccc00;
    public static var NUETRAL_COLOR = 0x999999;
    public static var ENEMY_COLOR = 0xcc0000;
    public static var SELF_COLOR = 0x0066ff;
    
    private static var options : MapOptions;
    private static var dimensions : Vector;
    private static var map : Sprite;
    private static var normalScans : Sprite;
    private static var penScans : Sprite;
    private static var image : Sprite;
    private static var player : Player;
    private static var starNames : Array<TextField>;
    private static var namesHolder : Sprite;
    
    /**
        Draws the map from the given player's perspective, using the given 
        options.
        
        This relies on the data being held in [Global].
    **/
    public static function getImage(mapOptions : MapOptions, pointOfView : Player) : Sprite {
        options = mapOptions;
        player = pointOfView;
        
        image = new Sprite();
        map = new Sprite();
        normalScans = new Sprite();
        penScans = new Sprite();
        namesHolder = new Sprite();
        
        
        image.addChild(normalScans);
        image.addChild(penScans);
        image.addChild(map);
        image.addChild(namesHolder);
        
        dimensions = new Vector(0, 0);
        for (star in Global.stars) {
            if (star.location.x > dimensions.x) dimensions.x = star.location.x;
            if (star.location.y > dimensions.y) dimensions.y = star.location.y;
        }
        
        starNames = new Array(); 
        
        renderBackground();
        handleScanners();
        renderGrid();
        
        for (star in Global.stars) renderStar(star, player);
    }
    
    private static inline function renderBackground() {
        image.graphics.beginFill(DEFAULT_BACKGROUND_COLOR);
        image.graphics.drawRect(0, 0, dimensions.x, dimensions.y);
        image.graphics.endFill();
    }
    
    private static inline function renderGrid() {
        if (options.drawGrid) {
            map.graphics.lineStyle(0.25, GRID_COLOR, 0.1);
        
            for (i in 1...Math.ceil(dimensions.x / GRID_SPACING)) {
                map.graphics.moveTo(i * GRID_SPACING, 0);
                map.graphics.lineTo(i * GRID_SPACING, dimensions.y);
            }
                
            for (j in 1...Math.ceil(dimensions.y / GRID_SPACING)) {
                map.graphics.moveTo(0, j * GRID_SPACING);
                map.graphics.lineTo(dimensions.x, j * GRID_SPACING);
            }
        }
    }
    
    private static function renderScans(points : Array<ScanPoint>, normal : Bool) {
        var maskSprite = new Sprite();
        var colorSprite = if (normal) normalScans else penScans;
        colorSprite.graphics.beginFill(if (normal) NORMAL_SCAN_COLOR else 
                                                   PENETRATING_SCAN_COLOR);
        colorSprite.graphics.drawRect(0, 0, mapWidth, mapHeight);
        colorSprite.graphics.endFill();
        
        maskSprite.graphics.lineStyle(0);
        for (point in points) {
            maskSprite.graphics.beginFill(0xFFFFFF);
            maskSprite.graphics.drawCircle(point.location.x, point.location.y, 
                                  point.radius);
            maskSprite.graphics.endFill();
        }
        
        colorSprite.mask = maskSprite;
        image.addChild(maskSprite);
        maskSprite.visible = false;
    }
    
    private static inline function renderStar(s : Star, p : Player) {
        var zoomFactor = options.zoom;
        if (s.concentrations == null || s.environment == null) {
            map.graphics.lineStyle(LINE_THICKNESS, UNKNOWN_STAR);
            map.graphics.drawCircle(s.location.x, s.location.y, MIN_RADIUS);
        } else {
            var habPercent = s.getHabPercent(p.race);
            
            var color = 
                if (habPercent < 0) 
                    Utils.mixColors(MIN_RED_STAR, MAX_RED_STAR, habPercent / -0.45)
                else
                    Utils.mixColors(MIN_GREEN_STAR, MAX_GREEN_STAR, habPercent);
                    
            var thickness = LINE_THICKNESS;
            //if (habPercent > 0) thickness += GREEN_ADDED_THICKNESS;
            
            map.graphics.lineStyle(thickness, color);
            
            var size = 
                if (s.isHomeworld != null && s.isHomeworld)
                    HW_RADIUS
                else if (s.population != null || s.population == 0) 
                    MIN_RADIUS
                else
                    Math.max(MIN_POP_RADIUS, 
                            MAX_RADIUS * (s.population / Star.MAX_POPULATION));
            
            
                    
            map.graphics.drawCircle(s.location.x, s.location.y, size);
            
            map.graphics.lineStyle(0.25, color);
            map.graphics.drawCircle(s.location.x, s.location.y, 0.1);
            
            //draw in the name
            var name = new TextField();
            name.width = 100;
            name.height = 30;
            name.selectable = false;
            name.textColor = 0xFFFFFF;
            name.text = s.name;
            
            var format = new TextFormat();
            format.align = TextFormatAlign.CENTER;
            format.size = 6;
            name.setTextFormat(format);
            
            name.x = s.location.x - 100 / 2;
            name.y = s.location.y + size;
            
            namesHolder.addChild(name);
            starNames.push(name);
            
            if (s.owner != null && s.owner.id != null && s.owner.id != -1) {
                if (s.owner.id == p.id) {
                    drawSelfFlag(s.location.x, s.location.y);
                } else if (p.relations != null) {
                    drawOtherFlag(s.location.x, s.location.y, 
                                  p.relations.getRelationTo(s.owner));
                }
            }
        }      
    }
    
    private static inline function drawFlag(x : Float, y : Float, color : Int) {
        x = Math.round(x);
        y = Math.round(y);
        
        map.graphics.lineStyle(FLAG_LINE, color);
        map.graphics.moveTo(x, y);
        map.graphics.lineTo(x, y - 13);
        
        map.graphics.drawRect(x + 1.5, y - 13, 10, 5);
    }
    
    private static inline function drawOtherFlag(x : Float, y : Float, 
                                            relation : Relation) {
        drawFlag(x, y, switch (relation) {
            case Relation.Friend: FRIENDLY_COLOR;
            case Relation.Nuetral: NUETRAL_COLOR;
            case Relation.Enemy: ENEMY_COLOR;
        });
    }
    
    private static inline function drawSelfFlag(x : Float, y : Float) {
        drawFlag(x, y, SELF_COLOR);
    }
    
    private static inline function handleScanners(stars : StarsHolder, 
                                                 fleets : FleetHolder, 
                                                 player : Player) {
        if (!options.drawScanners) return;
        
        //mostly a copy from [Scanning]
        var penPoints = new Array<ScanPoint>();
        var normalPoints = new Array<ScanPoint>();
        
        normalPoints.push({radius : 70, location : new Vector(50, 50)});
        normalPoints.push({radius : 40, location : new Vector(70, 90)});
        
        //add star points
        var scanner = player.starScanner;
        if(scanner != null) {
            var nRadius = scanner.range;
            var pRadius = scanner.penetratingRange;
            
            if (scanner.isPenetrating()) {
                for (star in stars) {
                    if (star == null) continue;
                    if (star.owner.id != player.id) continue;
                    if (star.hasScanner == null || !star.hasScanner) continue;
                    
                    penPoints.push( 
                            {radius : pRadius, 
                             location : star.location.clone()} );
                    normalPoints.push( 
                            {radius : nRadius, 
                             location : star.location.clone()} );
                }
            } else {
                for (star in stars) {
                    if (star == null) continue;
                    if (star.owner == null || 
                            star.owner.id != player.id) continue;
                    if (star.hasScanner == null || !star.hasScanner) continue;
                    
                    normalPoints.push( {radius : nRadius, 
                                        location : star.location.clone()} );
                }
            }
        }
        
        //add fleet points
        for (fleet in fleets) {
            if (fleet.owner.id != player.id) continue;
            
            var pRadius = fleet.getPenetratingScanningRange();
            var nRadius = fleet.getNormalScanningRange();
            
            if (pRadius > 0) {
                penPoints.push( {radius : pRadius, location : fleet.location} );
            }
            if (nRadius > 0) {
                normalPoints.push( 
                        {radius : pRadius, location : fleet.location} );
            }
        }
        
        renderScans(penPoints, false);
        renderScans(normalPoints, true);
    }  
}

/**
    Pass an instance of this to [MapImage.getImage].
**/
class MapOptions {
    
    public var zoom : Float;
    public var drawStarNames : Bool;
    public var drawFleets : Bool;
    public var drawFleetNames : Bool;
    public var drawGrid : Bool;
    public var drawStarOwner : Bool;
    public var drawFleetAmounts : Bool;
    public var drawScanners : Bool;
    
    /**
        Creates a new set of options.
        Defaults:
            zoom = 1.0
            drawStarNames = true
            drawFleets = true
            drawFleetNames = false
            drawFleetAmounts = true
            drawGrid = true
            drawStarOwner = true
            drawScanners = true
    **/
    public function new() {
        zoom = 1.0;
        drawStarNames = true;
        drawFleets = true;
        drawFleetNames = false;
        drawFleetAmounts = true;
        drawGrid = true;
        drawStarOwner = true;
        drawScanners = true;
    }
}
